package Acme.Serve;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.Principal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

class ServeConnection implements Runnable, HttpServletRequest,
		HttpServletResponse {

	public final static String CONTENTTYPE = "Content-Type".toLowerCase();
	private String charEncoding, sessionIdString;
	public final static String ISO_8859_1 = "ISO-8859-1";
	public final static String WWWFORMURLENCODE = "application/x-www-form-urlencoded";
	private String[] postCache;
	private Socket socket;
	private Serve serve;
	private HttpSession session;

	private ServletInputStream in;
	private ServletOutputStream out;

	private Map<String, Cookie> cookies = null; // !!!

	// / Constructor.
	public ServeConnection(Socket socket, Serve serve) {
		// Save arguments.
		this.socket = socket;
		this.serve = serve;

		// Start a separate thread to read and handle the request.
		Thread thread = new Thread(this);
		thread.start();
	}

	// Methods from Runnable.

	private String reqMethod = null;
	private String reqUriPath = null;
	private String reqProtocol = null;
	private boolean oneOne; // HTTP/1.1 or better
	private boolean reqMime;
	String reqQuery = null;
	private Vector<String> reqHeaderNames = new Vector<String>();
	private Vector<String> reqHeaderValues = new Vector<String>();

	public void run() {
		try {
			// Get the streams.
			in = new ServeInputStream(socket.getInputStream());
			out = new ServeOutputStream(socket.getOutputStream(), this);
		} catch (IOException e) {
			problem("Getting streams: " + e.getMessage(), SC_BAD_REQUEST);
		}

		parseRequest();

		try {
			socket.close();
		} catch (IOException e) { /* ignore */
		}
	}

	// / Turns a String into an array of Strings, by using StringTokenizer
	// to split it up at whitespace.
	public static String[] splitStr(String str) {
		StringTokenizer st = new StringTokenizer(str);
		int n = st.countTokens();
		String[] strs = new String[n];
		for (int i = 0; i < n; ++i)
			strs[i] = st.nextToken();
		return strs;
	}

	private void parseRequest() {
		byte[] lineBytes = new byte[4096];
		int len;
		String line;

		try {
			// Read the first line of the request.
			len = in.readLine(lineBytes, 0, lineBytes.length);
			if (len == -1 || len == 0) {
				problem("Empty request", SC_BAD_REQUEST);
				return;
			}
			line = new String(lineBytes, 0, len);
			String[] tokens = splitStr(line);
			switch (tokens.length) {
			case 2:
				// Two tokens means the protocol is HTTP/0.9.
				reqProtocol = "HTTP/0.9";
				oneOne = false;
				reqMime = false;
				break;
			case 3:
				reqProtocol = tokens[2];
				oneOne = !reqProtocol.toUpperCase().equals("HTTP/1.0");
				reqMime = true;
				// Read the rest of the lines.
				while (true) {
					len = in.readLine(lineBytes, 0, lineBytes.length);
					if (len == -1 || len == 0)
						break;
					line = new String(lineBytes, 0, len);
					int colonBlank = line.indexOf(": ");
					if (colonBlank != -1) {
						String name = line.substring(0, colonBlank);
						String value = line.substring(colonBlank + 2);
						reqHeaderNames.addElement(name.toLowerCase());
						reqHeaderValues.addElement(value);
					}
				}
				break;
			default:
				problem("Malformed request line", SC_BAD_REQUEST);
				return;
			}
			reqMethod = tokens[0];
			reqUriPath = tokens[1];

			// Check Host: header in HTTP/1.1 requests.
			if (oneOne) {
				String host = getHeader("host");
				if (host == null) {
					problem("Host header missing on HTTP/1.1 request",
							SC_BAD_REQUEST);
					return;
				}
				// !!!
			}

			// Split off query string, if any.
			int qmark = reqUriPath.indexOf('?');
			if (qmark != -1) {
				reqQuery = reqUriPath.substring(qmark + 1);
				reqUriPath = reqUriPath.substring(0, qmark);
			}

			// Decode %-sequences.
			reqUriPath = decode(reqUriPath, "UTF-8");
			int sessionIdIndex = reqUriPath.indexOf(';');
			if (sessionIdIndex != -1) {
				sessionIdString = reqUriPath.substring(sessionIdIndex + 1);
				reqUriPath = reqUriPath.substring(0, sessionIdIndex);
			}
			cookies = getCookiesFromRequest();
			session = ServeSessionManager.getInstance().getSession(
					sessionIdString, cookies);
			queryParameters = getParametersFromRequest();

			Servlet servlet = (Servlet) serve.registry.get(reqUriPath);
			if (servlet != null)
				runServlet((HttpServlet) servlet);
		} catch (IOException e) {
			problem("Reading request: " + e.getMessage(), SC_BAD_REQUEST);
		}
	}

	private Map<String, Cookie> getCookiesFromRequest() {
		Map<String, Cookie> cookies = new HashMap<String, Cookie>();
		String cookiesStr = getHeader("Cookie");
		if (cookiesStr != null && !("".equals(cookiesStr.trim()))) {
			String[] cookiePairs = cookiesStr.split(";");
			String[] cookieNameValue = null;
			for (String cookieStr : cookiePairs) {
				if (!cookieStr.trim().equals("")) {
					cookieNameValue = cookieStr.trim().split("=");
					cookies.put(cookieNameValue[0], new Cookie(
							cookieNameValue[0], cookieNameValue[1]));
				}
			}
		}
		return cookies;
	}

	private void runServlet(HttpServlet servlet) {
		// Set default response fields.
		setStatus(SC_OK);
		setDateHeader("Date", System.currentTimeMillis());
		setHeader("Server", ServeUtils.serverName + "/"
				+ ServeUtils.serverVersion);
		setHeader("Connection", "close");
		try {
			servlet.service(this, this);
		} catch (IOException e) {
			problem("IO problem running servlet: " + e.toString(),
					SC_BAD_REQUEST);
		} catch (ServletException e) {
			problem("problem running servlet: " + e.toString(), SC_BAD_REQUEST);
		} catch (Exception e) {
			e.printStackTrace();
			problem("unexpected problem running servlet: " + e.toString(),
					SC_INTERNAL_SERVER_ERROR);
		}
	}

	private void problem(String logMessage, int resCode) {
		serve.log(logMessage);
		try {
			sendError(resCode);
		} catch (IOException e) { /* ignore */
		}
	}

	private static final int sText = 0;

	private static final int s1Dig = 1;

	private static final int s2Dig = 2;

	private static final int sEscape = 3;

	private static final int sU1 = 4;

	private static final int sU2 = 5;

	private static final int sU3 = 6;

	private static final int sU4 = 7;

	/**
	 * Decodes URL encoded string including newly introduced JavaScript encoding
	 * with %uxxxx chars
	 * 
	 * @param s
	 *            encoded string
	 * @param enc
	 *            source encoding
	 * @return decoded string or original if no decoding required
	 * @throws UnsupportedEncodingException
	 */
	public static String decode(String s, String enc)
			throws UnsupportedEncodingException {
		if (enc == null || enc.length() == 0) {
			throw new UnsupportedEncodingException(
					"decode: no source char encoding provided.");
		}
		boolean decoded = false;
		int l = s.length();
		StringBuffer sb = new StringBuffer(l > 1024 ? l / 3 : l);

		int state = sText;
		int i = 0;
		int code = 0;
		char c;
		int pos = 0;
		int ofs = 0;
		byte[] buf = null;
		boolean processDig = false;
		while (i < l) {
			c = s.charAt(i);
			switch (c) {
			case '+':
				decoded = true;
				if (state == sText)
					sb.append(' ');
				else if (state == s2Dig) {
					sb.append(new String(buf, 0, pos + 1, enc));
					state = sText;
					sb.append(' ');
				} else
					new IllegalArgumentException(
							"decode: unexpected + at pos: " + i + ", of : " + s);
				break;
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				ofs = '0';
				processDig = true;
				break;
			case 'a':
			case 'b':
			case 'c':
			case 'd':
			case 'e':
			case 'f':
				ofs = 'a' - 10;
				processDig = true;
				break;
			case 'A':
			case 'B':
			case 'C':
			case 'D':
			case 'E':
			case 'F':
				ofs = 'A' - 10;
				processDig = true;
				break;
			case '%':
				decoded = true;
				if (state == sText) {
					state = sEscape;
					if (buf == null)
						buf = new byte[(l - i) / 3];
					pos = 0;
				} else if (state == s2Dig) {
					state = sEscape;
					pos++;
				} else
					new IllegalArgumentException(
							"decode: unexpected escape % at pos: " + i
									+ ", of : " + s);
				break;
			case 'u':
				if (state == sEscape) {
					if (pos > 0) {
						sb.append(new String(buf, 0, pos, enc));
						pos = 0;
					}
					state = sU1;
				} else if (state == sText) {
					sb.append(c);
				} else if (state == s2Dig) {
					sb.append(new String(buf, 0, pos + 1, enc));
					state = sText;
					sb.append(c);
				} else
					new IllegalArgumentException(
							"decode: unexpected char in hex at pos: " + i
									+ ", of : " + s);
				break;
			default:
				if (state == sText)
					sb.append(c);
				else if (state == s2Dig) {
					sb.append(new String(buf, 0, pos + 1, enc));
					state = sText;
					sb.append(c);
				} else
					new IllegalArgumentException(
							"decode: unexpected char in hex at pos: " + i
									+ ", of : " + s);

				break;
			}
			i++;
			if (processDig) {
				if (state == sEscape) {
					code = c - ofs;
					state = s1Dig;
				} else if (state == s1Dig) {
					buf[pos] = (byte) (code * 16 + (c - ofs));
					state = s2Dig;
				} else if (state == s2Dig) { // escape finished
					sb.append(new String(buf, 0, pos + 1, enc));
					state = sText;
					sb.append(c);
				} else if (state == sU1) {
					code = c - ofs;
					state = sU2;
				} else if (state == sU2) {
					code = code * 16 + c - ofs;
					state = sU3;
				} else if (state == sU3) {
					code = code * 16 + c - ofs;
					state = sU4;
				} else if (state == sU4) {
					sb.append((char) (code * 16 + c - ofs));
					state = sText;
				} else
					sb.append(c);
				processDig = false;
			}
		}
		if (state == s2Dig)
			sb.append(new String(buf, 0, pos + 1, enc));
		return (decoded ? sb.toString() : s);
	}

	// Methods from ServletRequest.

	// / Returns the size of the request entity data, or -1 if not known.
	// Same as the CGI variable CONTENT_LENGTH.
	public int getContentLength() {
		return getIntHeader("content-length", -1);
	}

	// / Returns the MIME type of the request entity data, or null if
	// not known.
	// Same as the CGI variable CONTENT_TYPE.
	public String getContentType() {
		return getHeader("content-type");
	}

	// / Returns the protocol and version of the request as a string of
	// the form <protocol>/<major version>.<minor version>.
	// Same as the CGI variable SERVER_PROTOCOL.
	public String getProtocol() {
		return reqProtocol;
	}

	// / Returns the scheme of the URL used in this request, for example
	// "http", "https", or "ftp". Different schemes have different rules
	// for constructing URLs, as noted in RFC 1738. The URL used to create
	// a request may be reconstructed using this scheme, the server name
	// and port, and additional information such as URIs.
	public String getScheme() {
		return "http";
	}

	// / Returns the host name of the server as used in the <host> part of
	// the request URI.
	// Same as the CGI variable SERVER_NAME.
	public String getServerName() {
		try {
			return InetAddress.getLocalHost().getHostName();
		} catch (UnknownHostException e) {
			return null;
		}
	}

	// / Returns the port number on which this request was received as used in
	// the <port> part of the request URI.
	// Same as the CGI variable SERVER_PORT.
	public int getServerPort() {
		return socket.getLocalPort();
	}

	// / Returns the IP address of the agent that sent the request.
	// Same as the CGI variable REMOTE_ADDR.
	public String getRemoteAddr() {
		return socket.getInetAddress().toString();
	}

	// / Returns the fully qualified host name of the agent that sent the
	// request.
	// Same as the CGI variable REMOTE_HOST.
	public String getRemoteHost() {
		return socket.getInetAddress().getHostName();
	}

	// / Applies alias rules to the specified virtual path and returns the
	// corresponding real path, or null if the translation can not be
	// performed for any reason. For example, an HTTP servlet would
	// resolve the path using the virtual docroot, if virtual hosting is
	// enabled, and with the default docroot otherwise. Calling this
	// method with the string "/" as an argument returns the document root.
	public String getRealPath(String path) {
		return serve.getRealPath(path);
	}

	// / Returns an input stream for reading request data.
	// @exception IllegalStateException if getReader has already been called
	// @exception IOException on other I/O-related errors
	public ServletInputStream getInputStream() throws IOException {
		return in;
	}

	// / Returns a buffered reader for reading request data.
	// @exception UnsupportedEncodingException if the character set encoding
	// isn't supported
	// @exception IllegalStateException if getInputStream has already been
	// called
	// @exception IOException on other I/O-related errors
	public BufferedReader getReader() {
		// !!!
		return null;
	}

	Map<String, String[]> queryParameters = null;

	// / Returns the parameter names for this request.
	public Enumeration<String> getParameterNames() {
		if (queryParameters == null) {
			queryParameters = getParametersFromRequest();
		}
		return new Vector<String>(queryParameters.keySet()).elements();
	}

	private synchronized Map<String, String[]> getParametersFromRequest() {
		Map<String, String[]> result = null;
		if ("GET".equals(reqMethod)) {
			if (reqQuery != null)
				try {
					result = parseQueryString(reqQuery, charEncoding);
				} catch (IllegalArgumentException ex) {
					serve.log("Exception " + ex + " at parsing 'get' data "
							+ reqQuery);
				}
		} else if ("POST".equals(reqMethod)) {
			String contentType = getContentType();
			if (contentType != null
					&& WWWFORMURLENCODE.regionMatches(true, 0, contentType, 0,
							WWWFORMURLENCODE.length())) {
				if (postCache == null) {
					postCache = new String[1];
					try {
						result = parsePostData(getContentLength(),
								getInputStream(), charEncoding, postCache);
					} catch (Exception ex) {
						serve.log("Exception " + ex
								+ " at parsing 'POST' data of length "
								+ getContentLength());
						// TODO propagate the exception ?
						return new HashMap<String, String[]>();
					}
				} else
					result = parseQueryString(postCache[0], charEncoding);
				if (reqQuery != null && reqQuery.length() > 0)
					result.putAll(parseQueryString(reqQuery, charEncoding));
			} else if (ServletFileUpload.isMultipartContent(this)) {
				result = new HashMap<String, String[]>();
				FileItemFactory factory = new DiskFileItemFactory();
				ServletFileUpload upload = new ServletFileUpload(factory);
				try {
					List<?> items = upload.parseRequest(this);
					for (Object item : items) {
						FileItem fileItem = (FileItem) item;
						if (fileItem.isFormField()) {
							result.put(fileItem.getFieldName(),
									new String[] { fileItem.getString() });
						} else {
							this.getSession().setAttribute(
									fileItem.getFieldName(), fileItem);
						}
					}
				} catch (FileUploadException e) {
					throw new RuntimeException(
							"Error reading multipart form data", e);
				} catch (Exception e) {
					throw new RuntimeException(
							"Error saving uploaded file to disk", e);
				}
			} else if (reqQuery != null)
				result = parseQueryString(reqQuery, charEncoding);
		}
		return result != null ? result : new HashMap<String, String[]>();
	}

	public static Map<String, String[]> parseQueryString(String query,
			String encoding) {
		Map<String, String[]> result = new HashMap<String, String[]>();
		if (encoding == null)
			encoding = "UTF-8";
		StringTokenizer st = new StringTokenizer(query, "&");
		while (st.hasMoreTokens()) {
			String pair = st.nextToken();
			int ep = pair.indexOf('=');
			String key = ep > 0 ? pair.substring(0, ep) : pair;
			String value = ep > 0 ? pair.substring(ep + 1) : "";
			try {
				key = /* URLDecoder. */decode(key, encoding);
				if (value != null)
					value = /* URLDecoder. */decode(value, encoding);
			} catch (UnsupportedEncodingException uee) {
			}
			String[] values = (String[]) result.get(key);
			String[] newValues;
			if (values == null) {
				newValues = new String[1];
				newValues[0] = value;
			} else {
				newValues = new String[values.length + 1];
				System.arraycopy(values, 0, newValues, 0, values.length);
				newValues[values.length] = value;
			}
			result.put(key, newValues);
		}
		return result;
	}

	public static Map<String, String[]> parsePostData(long len, InputStream is,
			String encoding, String[] cachedStream) throws IOException {
		// TODO: handle parsing data over 2 GB
		if (len > Integer.MAX_VALUE)
			throw new RuntimeException("Can't process POST data over "
					+ Integer.MAX_VALUE + ", requested: " + len);
		byte[] buf = new byte[(int) len];
		int fp = 0;
		while (fp < len) {
			int c = is.read(buf, fp, buf.length - fp);
			if (c < 0)
				break;
			fp += c;
		}
		// System.err.println("====>"+new String( buf));
		if (cachedStream != null && cachedStream.length > 0)
			return parseQueryString(cachedStream[0] = new String(buf, 0, fp,
					ISO_8859_1), encoding);
		else
			return parseQueryString(new String(buf, 0, fp, ISO_8859_1),
					encoding);
	}

	// / Returns the value of the specified query string parameter, or null
	// if not found.
	// @param name the parameter name
	public String getParameter(String name) {
		String[] vals = queryParameters.get(name);
		if (vals != null && vals.length > 0)
			return vals[0];
		else
			return null;
	}

	// / Returns the values of the specified parameter for the request as an
	// array of strings, or null if the named parameter does not exist.
	public String[] getParameterValues(String name) {
		String[] result = new String[queryParameters.size()];
		queryParameters.values().toArray(result);
		return result;
	}

	// / Returns the value of the named attribute of the request, or null if
	// the attribute does not exist. This method allows access to request
	// information not already provided by the other methods in this interface.
	public Object getAttribute(String name) {
		// This server does not implement attributes.
		return null;
	}

	// Methods from HttpServletRequest.

	// / Gets the array of cookies found in this request.
	public Cookie[] getCookies() {
		Cookie[] cookieArray = new Cookie[cookies.size()];
		cookies.values().toArray(cookieArray);
		return cookieArray;
	}

	// / Returns the method with which the request was made. This can be "GET",
	// "HEAD", "POST", or an extension method.
	// Same as the CGI variable REQUEST_METHOD.
	public String getMethod() {
		return reqMethod;
	}

	// / Returns the full request URI.
	public String getRequestURI() {
		String portPart = "";
		int port = getServerPort();
		if (port != 80)
			portPart = ":" + port;
		String queryPart = "";
		String queryString = getQueryString();
		if (queryString != null && queryString.length() > 0)
			queryPart = "?" + queryString;
		return "http://" + getServerName() + portPart + reqUriPath + queryPart;
	}

	// / Returns the part of the request URI that referred to the servlet being
	// invoked.
	// Analogous to the CGI variable SCRIPT_NAME.
	public String getServletPath() {
		// In this server, the entire path is regexp-matched against the
		// servlet pattern, so there's no good way to distinguish which
		// part refers to the servlet.
		return reqUriPath;
	}

	// / Returns optional extra path information following the servlet path, but
	// immediately preceding the query string. Returns null if not specified.
	// Same as the CGI variable PATH_INFO.
	public String getPathInfo() {
		// In this server, the entire path is regexp-matched against the
		// servlet pattern, so there's no good way to distinguish which
		// part refers to the servlet.
		return null;
	}

	// / Returns extra path information translated to a real path. Returns
	// null if no extra path information was specified.
	// Same as the CGI variable PATH_TRANSLATED.
	public String getPathTranslated() {
		// In this server, the entire path is regexp-matched against the
		// servlet pattern, so there's no good way to distinguish which
		// part refers to the servlet.
		return null;
	}

	// / Returns the query string part of the servlet URI, or null if not known.
	// Same as the CGI variable QUERY_STRING.
	public String getQueryString() {
		return reqQuery;
	}

	// / Returns the name of the user making this request, or null if not known.
	// Same as the CGI variable REMOTE_USER.
	public String getRemoteUser() {
		// This server does not support authentication, so even if a username
		// is supplied in the headers we don't want to look at it.
		return null;
	}

	// / Returns the authentication scheme of the request, or null if none.
	// Same as the CGI variable AUTH_TYPE.
	public String getAuthType() {
		// This server does not support authentication.
		return null;
	}

	// / Returns the value of a header field, or null if not known.
	// Same as the information passed in the CGI variabled HTTP_*.
	// @param name the header field name
	public String getHeader(String name) {
		int i = reqHeaderNames.indexOf(name.toLowerCase());
		if (i == -1)
			return null;
		return (String) reqHeaderValues.elementAt(i);
	}

	// / Returns the value of an integer header field.
	// @param name the header field name
	// @param def the integer value to return if header not found or invalid
	public int getIntHeader(String name, int def) {
		String val = getHeader(name);
		if (val == null)
			return def;
		try {
			return Integer.parseInt(val);
		} catch (Exception e) {
			return def;
		}
	}

	// / Returns the value of a long header field.
	// @param name the header field name
	// @param def the long value to return if header not found or invalid
	public long getLongHeader(String name, long def) {
		String val = getHeader(name);
		if (val == null)
			return def;
		try {
			return Long.parseLong(val);
		} catch (Exception e) {
			return def;
		}
	}

	// / Returns the value of a date header field.
	// @param name the header field name
	// @param def the date value to return if header not found or invalid
	public long getDateHeader(String name, long def) {
		String val = getHeader(name);
		if (val == null)
			return def;
		try {
			return DateFormat.getDateInstance().parse(val).getTime();
		} catch (Exception e) {
			return def;
		}
	}

	// / Returns an Enumeration of the header names.
	public Enumeration<String> getHeaderNames() {
		return reqHeaderNames.elements();
	}

	// Session stuff. Not implemented, but the API is here for compatibility.

	// / Gets the current valid session associated with this request, if
	// create is false or, if necessary, creates a new session for the
	// request, if create is true.
	// <P>
	// Note: to ensure the session is properly maintained, the servlet
	// developer must call this method (at least once) before any output
	// is written to the response.
	// <P>
	// Additionally, application-writers need to be aware that newly
	// created sessions (that is, sessions for which HttpSession.isNew
	// returns true) do not have any application-specific state.
	public HttpSession getSession(boolean create) {
		return null;
	}

	// / Gets the session id specified with this request. This may differ
	// from the actual session id. For example, if the request specified
	// an id for an invalid session, then this will get a new session with
	// a new id.
	public String getRequestedSessionId() {
		return null;
	}

	// / Checks whether this request is associated with a session that is
	// valid in the current session context. If it is not valid, the
	// requested session will never be returned from the getSession
	// method.
	public boolean isRequestedSessionIdValid() {
		return false;
	}

	// / Checks whether the session id specified by this request came in as
	// a cookie. (The requested session may not be one returned by the
	// getSession method.)
	public boolean isRequestedSessionIdFromCookie() {
		return false;
	}

	// / Checks whether the session id specified by this request came in as
	// part of the URL. (The requested session may not be the one returned
	// by the getSession method.)
	public boolean isRequestedSessionIdFromUrl() {
		return false;
	}

	// Methods from ServletResponse.

	// / Sets the content length for this response.
	// @param length the content length
	public void setContentLength(int length) {
		setIntHeader("Content-Length", length);
	}

	// / Sets the content type for this response.
	// @param type the content type
	public void setContentType(String type) {
		setHeader("Content-Type", type);
	}

	// / Returns an output stream for writing response data.
	public ServletOutputStream getOutputStream() {
		return out;
	}

	// / Returns a print writer for writing response data. The MIME type of
	// the response will be modified, if necessary, to reflect the character
	// encoding used, through the charset=... property. This means that the
	// content type must be set before calling this method.
	// @exception UnsupportedEncodingException if no such encoding can be
	// provided
	// @exception IllegalStateException if getOutputStream has been called
	// @exception IOException on other I/O errors
	public PrintWriter getWriter() throws IOException {
		// !!!
		return null;
	}

	// / Returns the character set encoding used for this MIME body. The
	// character encoding is either the one specified in the assigned
	// content type, or one which the client understands. If no content
	// type has yet been assigned, it is implicitly set to text/plain.
	public String getCharacterEncoding() {
		if (resHeaderNames.contains(CONTENTTYPE.toLowerCase())) {
			String enc = extractEncodingFromContentType(CONTENTTYPE
					.toLowerCase());
			if (enc != null)
				return enc;
		}
		return charEncoding;
	}

	private String extractEncodingFromContentType(String ct) {
		if (ct == null)
			return null;
		int scp = ct.indexOf(';');
		if (scp > 0) {
			scp = ct.toLowerCase().indexOf("charset=", scp);
			if (scp >= 0) {
				ct = ct.substring(scp + "charset=".length()).trim();
				scp = ct.indexOf(';');
				if (scp > 0)
					ct = ct.substring(0, scp);
				int l = ct.length();
				if (l > 2 && ct.charAt(0) == '"')
					return ct.substring(1, l - 1);
				return ct;
			}
		}
		return null;
	}

	// Methods from HttpServletResponse.

	// / Adds the specified cookie to the response. It can be called
	// multiple times to set more than one cookie.
	public void addCookie(Cookie cookie) {
		cookies.put(cookie.getName(), cookie);
	}

	// / Checks whether the response message header has a field with the
	// specified name.
	public boolean containsHeader(String name) {
		return resHeaderNames.contains(name);
	}

	private int resCode = -1;
	private String resMessage = null;
	private Vector<String> resHeaderNames = new Vector<String>();
	private Vector<String> resHeaderValues = new Vector<String>();

	// / Sets the status code and message for this response.
	// @param resCode the status code
	// @param resMessage the status message
	public void setStatus(int resCode, String resMessage) {
		this.resCode = resCode;
		this.resMessage = resMessage;
	}

	// / Sets the status code and a default message for this response.
	// @param resCode the status code
	public void setStatus(int resCode) {
		switch (resCode) {
		case SC_CONTINUE:
			setStatus(resCode, "Continue");
			break;
		case SC_SWITCHING_PROTOCOLS:
			setStatus(resCode, "Switching protocols");
			break;
		case SC_OK:
			setStatus(resCode, "OK");
			break;
		case SC_CREATED:
			setStatus(resCode, "Created");
			break;
		case SC_ACCEPTED:
			setStatus(resCode, "Accepted");
			break;
		case SC_NON_AUTHORITATIVE_INFORMATION:
			setStatus(resCode, "Non-authoritative");
			break;
		case SC_NO_CONTENT:
			setStatus(resCode, "No content");
			break;
		case SC_RESET_CONTENT:
			setStatus(resCode, "Reset content");
			break;
		case SC_PARTIAL_CONTENT:
			setStatus(resCode, "Partial content");
			break;
		case SC_MULTIPLE_CHOICES:
			setStatus(resCode, "Multiple choices");
			break;
		case SC_MOVED_PERMANENTLY:
			setStatus(resCode, "Moved permanentently");
			break;
		case SC_MOVED_TEMPORARILY:
			setStatus(resCode, "Moved temporarily");
			break;
		case SC_SEE_OTHER:
			setStatus(resCode, "See other");
			break;
		case SC_NOT_MODIFIED:
			setStatus(resCode, "Not modified");
			break;
		case SC_USE_PROXY:
			setStatus(resCode, "Use proxy");
			break;
		case SC_BAD_REQUEST:
			setStatus(resCode, "Bad request");
			break;
		case SC_UNAUTHORIZED:
			setStatus(resCode, "Unauthorized");
			break;
		case SC_PAYMENT_REQUIRED:
			setStatus(resCode, "Payment required");
			break;
		case SC_FORBIDDEN:
			setStatus(resCode, "Forbidden");
			break;
		case SC_NOT_FOUND:
			setStatus(resCode, "Not found");
			break;
		case SC_METHOD_NOT_ALLOWED:
			setStatus(resCode, "Method not allowed");
			break;
		case SC_NOT_ACCEPTABLE:
			setStatus(resCode, "Not acceptable");
			break;
		case SC_PROXY_AUTHENTICATION_REQUIRED:
			setStatus(resCode, "Proxy auth required");
			break;
		case SC_REQUEST_TIMEOUT:
			setStatus(resCode, "Request timeout");
			break;
		case SC_CONFLICT:
			setStatus(resCode, "Conflict");
			break;
		case SC_GONE:
			setStatus(resCode, "Gone");
			break;
		case SC_LENGTH_REQUIRED:
			setStatus(resCode, "Length required");
			break;
		case SC_PRECONDITION_FAILED:
			setStatus(resCode, "Precondition failed");
			break;
		case SC_REQUEST_ENTITY_TOO_LARGE:
			setStatus(resCode, "Request entity too large");
			break;
		case SC_REQUEST_URI_TOO_LONG:
			setStatus(resCode, "Request URI too large");
			break;
		case SC_UNSUPPORTED_MEDIA_TYPE:
			setStatus(resCode, "Unsupported media type");
			break;
		case SC_INTERNAL_SERVER_ERROR:
			setStatus(resCode, "Internal server error");
			break;
		case SC_NOT_IMPLEMENTED:
			setStatus(resCode, "Not implemented");
			break;
		case SC_BAD_GATEWAY:
			setStatus(resCode, "Bad gateway");
			break;
		case SC_SERVICE_UNAVAILABLE:
			setStatus(resCode, "Service unavailable");
			break;
		case SC_GATEWAY_TIMEOUT:
			setStatus(resCode, "Gateway timeout");
			break;
		case SC_HTTP_VERSION_NOT_SUPPORTED:
			setStatus(resCode, "HTTP version not supported");
			break;
		default:
			setStatus(resCode, "");
			break;
		}
	}

	// / Sets the value of a header field.
	// @param name the header field name
	// @param value the header field value
	public void setHeader(String name, String value) {
		resHeaderNames.addElement(name);
		resHeaderValues.addElement(value);
	}

	// / Sets the value of an integer header field.
	// @param name the header field name
	// @param value the header field integer value
	public void setIntHeader(String name, int value) {
		setHeader(name, Integer.toString(value));
	}

	// / Sets the value of a long header field.
	// @param name the header field name
	// @param value the header field long value
	public void setLongHeader(String name, long value) {
		setHeader(name, Long.toString(value));
	}

	// / Sets the value of a date header field.
	// @param name the header field name
	// @param value the header field date value
	public void setDateHeader(String name, long value) {
		setHeader(name, to1123String(new Date(value)));
	}

	// / Converts a Date into an RFC-1123 string.
	private static String to1123String(Date date) {
		// We have to go through some machinations here to get the
		// correct day of the week in GMT. getDay() gives the day in
		// local time. getDate() gives the day of the month in local
		// time. toGMTString() gives a formatted string in GMT. So, we
		// extract the day of the month from the GMT string, and if it
		// doesn't match the local one we change the local day of the
		// week accordingly.
		//
		// The Date class sucks.

		DateFormat formatter = new SimpleDateFormat(
				"EEE dd MMM yyyy HH:mm:ss z");
		return formatter.format(date);
	}

	private boolean headersWritten = false;

	// / Writes the status line and message headers for this response to the
	// output stream.
	// @exception IOException if an I/O error has occurred
	void writeHeaders() throws IOException {
		if (headersWritten)
			return;
		if (this.session.isNew()) {
			this.addCookie(new Cookie(ServeSessionManager.SESSION_COOKIE_NAME,
					session.getId()));
		}
		StringBuffer cookieStr = new StringBuffer();
		Iterator<Cookie> cookiesItr = this.cookies.values().iterator();
		Cookie cookie = null;
		while (cookiesItr.hasNext()) {
			cookie = cookiesItr.next();
			cookieStr.append(cookie.getName());
			cookieStr.append('=');
			cookieStr.append(cookie.getValue());
			if (cookiesItr.hasNext())
				cookieStr.append(';');
		}
		setHeader("Set-Cookie", cookieStr.toString());
		headersWritten = true;
		if (reqMime) {
			out.println(reqProtocol + " " + resCode + " " + resMessage);
			for (int i = 0; i < resHeaderNames.size(); ++i) {
				String name = (String) resHeaderNames.elementAt(i);
				String value = (String) resHeaderValues.elementAt(i);
				if (value != null) // just in case
					out.println(name + ": " + value);
			}
			out.println("");
			out.flush();
		}
	}

	// / Writes an error response using the specified status code and message.
	// @param resCode the status code
	// @param resMessage the status message
	// @exception IOException if an I/O error has occurred
	public void sendError(int resCode, String resMessage) throws IOException {
		setStatus(resCode, resMessage);
		realSendError();
	}

	// / Writes an error response using the specified status code and a default
	// message.
	// @param resCode the status code
	// @exception IOException if an I/O error has occurred
	public void sendError(int resCode) throws IOException {
		setStatus(resCode);
		realSendError();
	}

	private void realSendError() throws IOException {
		setContentType("text/html");
		out.println("<HTML><HEAD>");
		out.println("<TITLE>" + resCode + " " + resMessage + "</TITLE>");
		out.println("</HEAD><BODY BGCOLOR=\"#99cc99\">");
		out.println("<H2>" + resCode + " " + resMessage + "</H2>");
		String ua = getHeader("user-agent");
		if (ua != null && ua.matches(".*MSIE.*")) {
			out.println("<!--");
			for (int i = 0; i < 6; ++i)
				out
						.println("Padding so that MSIE deigns to show this error instead of its own canned one.");
			out.println("-->");
		}
		out.println("<HR>");
		ServeUtils.writeAddress(out);
		out.println("</BODY></HTML>");
		out.flush();
	}

	// / Sends a redirect message to the client using the specified redirect
	// location URL.
	// @param location the redirect location URL
	// @exception IOException if an I/O error has occurred
	public void sendRedirect(String location) throws IOException {
		setHeader("Location", location);
		sendError(SC_MOVED_TEMPORARILY);
	}

	// URL session-encoding stuff. Not implemented, but the API is here
	// for compatibility.

	// / Encodes the specified URL by including the session ID in it, or, if
	// encoding is not needed, returns the URL unchanged. The
	// implementation of this method should include the logic to determine
	// whether the session ID needs to be encoded in the URL. For example,
	// if the browser supports cookies, or session tracking is turned off,
	// URL encoding is unnecessary.
	// <P>
	// All URLs emitted by a Servlet should be run through this method.
	// Otherwise, URL rewriting cannot be used with browsers which do not
	// support cookies.
	public String encodeUrl(String url) {
		return url;
	}

	// / Encodes the specified URL for use in the sendRedirect method or, if
	// encoding is not needed, returns the URL unchanged. The
	// implementation of this method should include the logic to determine
	// whether the session ID needs to be encoded in the URL. Because the
	// rules for making this determination differ from those used to
	// decide whether to encode a normal link, this method is seperate
	// from the encodeUrl method.
	// <P>
	// All URLs sent to the HttpServletResponse.sendRedirect method should be
	// run through this method. Otherwise, URL rewriting cannot be used with
	// browsers which do not support cookies.
	public String encodeRedirectUrl(String url) {
		return url;
	}

	public String getContextPath() {
		// TODO Auto-generated method stub
		return null;
	}

	public long getDateHeader(String arg0) {
		// TODO Auto-generated method stub
		return 0;
	}

	public Enumeration<String> getHeaders(String arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	public int getIntHeader(String arg0) {
		// TODO Auto-generated method stub
		return 0;
	}

	public StringBuffer getRequestURL() {
		// TODO Auto-generated method stub
		return null;
	}

	public HttpSession getSession() {
		return this.session;
	}

	public Principal getUserPrincipal() {
		// TODO Auto-generated method stub
		return null;
	}

	public boolean isRequestedSessionIdFromURL() {
		// TODO Auto-generated method stub
		return false;
	}

	public boolean isUserInRole(String arg0) {
		// TODO Auto-generated method stub
		return false;
	}

	public Enumeration<String> getAttributeNames() {
		// TODO Auto-generated method stub
		return null;
	}

	public String getLocalAddr() {
		// TODO Auto-generated method stub
		return null;
	}

	public String getLocalName() {
		// TODO Auto-generated method stub
		return null;
	}

	public int getLocalPort() {
		// TODO Auto-generated method stub
		return 0;
	}

	public Locale getLocale() {
		// TODO Auto-generated method stub
		return null;
	}

	public Enumeration<Locale> getLocales() {
		// TODO Auto-generated method stub
		return null;
	}

	public Map<String, String> getParameterMap() {
		// TODO Auto-generated method stub
		return null;
	}

	public int getRemotePort() {
		// TODO Auto-generated method stub
		return 0;
	}

	public RequestDispatcher getRequestDispatcher(String arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	public boolean isSecure() {
		// TODO Auto-generated method stub
		return false;
	}

	public void removeAttribute(String arg0) {
		// TODO Auto-generated method stub

	}

	public void setAttribute(String arg0, Object arg1) {
		// TODO Auto-generated method stub

	}

	public void setCharacterEncoding(String arg0) {
		// TODO Auto-generated method stub

	}

	public void addDateHeader(String arg0, long arg1) {
		// TODO Auto-generated method stub

	}

	public void addHeader(String arg0, String arg1) {
		// TODO Auto-generated method stub

	}

	public void addIntHeader(String arg0, int arg1) {
		// TODO Auto-generated method stub

	}

	public String encodeRedirectURL(String arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	public String encodeURL(String arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	public void flushBuffer() throws IOException {
		// TODO Auto-generated method stub

	}

	public int getBufferSize() {
		// TODO Auto-generated method stub
		return 0;
	}

	public boolean isCommitted() {
		// TODO Auto-generated method stub
		return false;
	}

	public void reset() {
		// TODO Auto-generated method stub

	}

	public void resetBuffer() {
		// TODO Auto-generated method stub

	}

	public void setBufferSize(int arg0) {
		// TODO Auto-generated method stub

	}

	public void setLocale(Locale arg0) {
		// TODO Auto-generated method stub

	}

}
